/** 
 ** Copyright (c) 2010 Ushahidi Inc
 ** All rights reserved
 ** Contact: team@ushahidi.com
 ** Website: http://www.ushahidi.com
 ** 
 ** GNU Lesser General Public License Usage
 ** This file may be used under the terms of the GNU Lesser
 ** General Public License version 3 as published by the Free Software
 ** Foundation and appearing in the file LICENSE.LGPL included in the
 ** packaging of this file. Please review the following information to
 ** ensure the GNU Lesser General Public License version 3 requirements
 ** will be met: http://www.gnu.org/licenses/lgpl.html.	
 **	
 **
 ** If you have questions regarding the use of this file, please contact
 ** Ushahidi developers at team@ushahidi.com.
 ** 
 **/

package com.ushahidi.android.app.util;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Random;
import java.util.TimeZone;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.location.Criteria;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.provider.Settings;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
import android.widget.Toast;

import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.TileOverlayOptions;
import com.ushahidi.android.app.MainApplication;
import com.ushahidi.android.app.MapBoxTileProvider;
import com.ushahidi.android.app.OpenStreetMapTileProvider;
import com.ushahidi.android.app.Preferences;
import com.ushahidi.android.app.R;
import com.ushahidi.android.app.ui.phone.AboutActivity;
import com.ushahidi.android.app.ui.tablet.AboutFragment;

/**
 * This is a utility class that has common methods to be used by most clsses.
 * 
 * @author eyedol
 */
public class Util {

    private static NetworkInfo networkInfo;

    private static Random random = new Random();

    private static final String VALID_EMAIL_PATTERN = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@"
            + "[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";

    private static Pattern pattern;

    private static Matcher matcher;

    public static final int IO_BUFFER_SIZE = 8 * 1024;

    /**
     * joins two strings together
     * 
     * @param first
     * @param second
     * @return
     */
    public static String joinString(String first, String second) {
        return first.concat(second);
    }

    /**
     * Converts a string integer
     * 
     * @param value
     * @return
     */
    public static int toInt(String value) {
        return Integer.parseInt(value);
    }

    /**
     * Capitalize any string given to it.
     * 
     * @param text
     * @return capitalized string
     */
    public static String capitalizeString(String text) {
        if (text.length() == 0)
            return text;
        return text.substring(0, 1).toUpperCase(Locale.getDefault())
                + text.substring(1).toLowerCase(Locale.getDefault());
    }

    /**
     * Create csv
     * 
     * @param Vector <String> text
     * @return csv
     */
    public static String implode(Vector<String> text) {
        String implode = "";
        int i = 0;
        for (String value : text) {
            implode += i == text.size() - 1 ? value : value + ",";
            i++;
        }

        return implode;
    }

    /**
     * Is there internet connection
     */
    public static boolean isConnected(Context context) {

        ConnectivityManager connectivity = (ConnectivityManager) context
                .getSystemService(Context.CONNECTIVITY_SERVICE);

        networkInfo = connectivity.getActiveNetworkInfo();
        // NetworkInfo info

        if (networkInfo != null && networkInfo.isConnected()
                && networkInfo.isAvailable()) {
            return true;
        }
        return false;

    }

    /***
     * Gets the state of Airplane Mode. * @param context * @return true if
     * enabled.
     */
    public static boolean isAirplaneModeOn(Context context) {
        return Settings.System.getInt(context.getContentResolver(),
                Settings.System.AIRPLANE_MODE_ON, 0) != 0;
    }

    /**
     * Truncates any given text.
     * 
     * @param String text - the text to be truncated
     * @return String
     */
    public static String truncateText(String text) {
        if (text.length() > 30) {
            return text.substring(0, 25).trim() + "";
        } else {
            return text;
        }
    }

    /**
     * Limit a string to defined length
     * 
     * @param int limit - the total length
     * @param string limited - the limited string
     */
    public static String limitString(String value, int length) {
        StringBuilder buf = new StringBuilder(value);
        if (buf.length() > length) {
            buf.setLength(length);
            buf.append(" ...");
        }
        return buf.toString();
    }

    public static String formatDate(String dateFormat, String date,
            String toFormat) {
        return formatDate(dateFormat, date, toFormat, null, null);
    }

    public static String datePattern(String dateFormat, Date date) {
        return android.text.format.DateFormat.format(
                "MMMM dd, yyyy 'at' hh:mm:ss aaa", date).toString();

    }

    /**
     * Format date into more readable format.
     * 
     * @param date - the date to be formatted.
     * @return String
     */
    public static String formatDate(String dateFormat, String date,
            String toFormat, Locale fromLocale, Locale toLocale) {

        String formatted = "";

        DateFormat formatter = fromLocale == null ? new SimpleDateFormat(
                dateFormat) : new SimpleDateFormat(dateFormat,
                Locale.getDefault());
        try {
            Date dateStr = formatter.parse(date);
            formatted = formatter.format(dateStr);
            Date formatDate = formatter.parse(formatted);
            formatter = toLocale == null ? new SimpleDateFormat(toFormat,
                    Locale.getDefault()) : new SimpleDateFormat(toFormat,
                    toLocale);
            formatted = formatter.format(formatDate);

        } catch (ParseException e) {

            e.printStackTrace();
        }
        return formatted;
    }

    public static Date formatDate(String date) {

        final SimpleDateFormat PARSER = new SimpleDateFormat(
                "yyyy-MM-dd HH:mm:ss", Locale.getDefault());
        try {
            return new com.ushahidi.java.sdk.api.json.Date(PARSER.parse(date));
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return new Date();
    }

    /**
     * For debugging purposes. Append content of a string to a file
     * 
     * @param text
     */
    public static void appendLog(String text) {
        File logFile = new File(Environment.getExternalStorageDirectory(),
                "ush_log.txt");
        if (!logFile.exists()) {
            try {
                logFile.createNewFile();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        try {
            // BufferedWriter for performance, true to set append to file flag
            BufferedWriter buf = new BufferedWriter(new FileWriter(logFile,
                    true));
            buf.append(text);
            buf.newLine();
            buf.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * Extract Google geocode JSON data
     * 
     * @apram json_data - the json data to be formatted.
     * @return String
     */
    /*
     * public static String getFromLocation(double latitude, double longitude,
     * Context context) { String json_data = ""; int status = 0; JSONArray
     * jsonArray; try { if (Util.isConnected(context)) { MainGeocoder geoCoder =
     * new MainGeocoder(context); json_data = geoCoder.reverseGeocode(latitude,
     * longitude); } else { return ""; } if (json_data != null) { jsonObject =
     * new JSONObject(json_data); status =
     * jsonObject.getJSONObject("Status").getInt("code"); if (status == 200) {
     * jsonArray = jsonObject.getJSONArray("Placemark"); return
     * jsonArray.getJSONObject(0) .getJSONObject("AddressDetails")
     * .getJSONObject("Country") .getJSONObject("AdministrativeArea")
     * .getJSONObject("Locality") .getString("LocalityName"); } else { return
     * ""; } } } catch (JSONException e) { return ""; // e.printStackTrace(); }
     * catch (IOException e) { return ""; } return ""; }
     */

    /**
     * Show toast
     * 
     * @param Context - the application's context
     * @param Int - string resource id
     * @return void
     */
    public static void showToast(Context context, int i) {
        int duration = Toast.LENGTH_LONG;
        Toast.makeText(context, i, duration).show();
    }

    /**
     * Validates an email address Credits:
     * http://www.mkyong.com/regular-expressions
     * /how-to-validate-email-address-with-regular-expression/
     * 
     * @param String - email address to be validated
     * @return boolean
     */
    public static boolean validateEmail(String emailAddress) {
        if (!TextUtils.isEmpty(emailAddress)) {
            pattern = Pattern.compile(VALID_EMAIL_PATTERN);
            matcher = pattern.matcher(emailAddress);
            return matcher.matches();
        }
        return false;
    }

    /**
     * Delete content of a folder recursively.
     * 
     * @param String path - path to the directory.
     * @return void
     */
    public static void rmDir(String path) {
        File dir = new File(path);
        if (dir.isDirectory()) {

            String[] children = dir.list();
            Log.d("Directory", "dir.list returned some files" + children.length
                    + "--");
            for (int i = 0; i < children.length; i++) {
                File temp = new File(dir, children[i]);

                if (temp.isDirectory()) {

                    rmDir(temp.getName());
                } else {
                    temp.delete();
                }
            }

            dir.delete();
        } else {
            Log.d("Directory", "This is not a directory" + path);
        }
    }

    /**
     * Capitalize each word in a text.
     * 
     * @param String text - The text to be capitalized.
     * @return String
     */
    public static String capitalize(String text) {
        if (text != null) {
            String[] words = text.split("\\s");
            String capWord = "";
            for (String word : words) {

                capWord += capitalizeString(word) + " ";

                return capWord;
            }
        }
        return "";
    }

    /** this criteria will settle for less accuracy, high power, and cost */
    public static Criteria createCoarseCriteria() {

        Criteria c = new Criteria();
        c.setAccuracy(Criteria.ACCURACY_COARSE);
        c.setAltitudeRequired(false);
        c.setBearingRequired(false);
        c.setSpeedRequired(false);
        c.setCostAllowed(true);
        c.setPowerRequirement(Criteria.POWER_HIGH);
        return c;

    }

    /** this criteria needs high accuracy, high power, and cost */
    public static Criteria createFineCriteria() {

        Criteria c = new Criteria();
        c.setAccuracy(Criteria.ACCURACY_FINE);
        c.setAltitudeRequired(false);
        c.setBearingRequired(false);
        c.setSpeedRequired(false);
        c.setCostAllowed(true);
        c.setPowerRequirement(Criteria.POWER_HIGH);
        return c;

    }

    public static String generateFilename(boolean thumbnail) {
        if (thumbnail) {
            return randomString() + "_t.jpg";
        }

        return randomString() + ".jpg";
    }

    public static String randomString() {
        return Long.toString(Math.abs(random.nextLong()), 10);
    }

    /**
     * Checks that the device supports Camera.
     * 
     * @param Context context - The calling activity's context.
     * @return boolean - True if it supports otherwise false.
     */
    public static boolean deviceHasCamera(Context context) {

        PackageManager pm = context.getPackageManager();

        if (pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Checks that the device supports Camera supports auto focus.
     * 
     * @param Context context - The calling activity's context.
     * @return boolean - True if it supports otherwise false.
     */
    public static boolean deviceCameraHasAutofocus(Context context) {

        PackageManager pm = context.getPackageManager();

        if (pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_AUTOFOCUS)) {
            return true;
        } else {
            return false;
        }
    }

    public static boolean isHoneycomb() {
        // Can use static final constants like HONEYCOMB, declared in later
        // versions
        // of the OS since they are inlined at compile time. This is guaranteed
        // behavior.
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
    }

    public static boolean isTablet(Context context) {
        return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE;
    }

    public static boolean isHoneycombTablet(Context context) {
        return isHoneycomb() && isTablet(context);
    }

    @SuppressWarnings("deprecation")
    public static int getScreenWidth(Context context) {
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        return display.getWidth();
    }

    public static String getDateTime() {
        DateFormat df = new SimpleDateFormat("yyyy_MM_dd_hh_mm_ss", Locale.US);
        df.setTimeZone(TimeZone.getTimeZone("GMT"));
        return df.format(new Date());
    }

    public static String IMEI(Context context) {
        TelephonyManager TelephonyMgr = (TelephonyManager) context
                .getSystemService(Context.TELEPHONY_SERVICE);
        return TelephonyMgr.getDeviceId(); // Requires READ_PHONE_STATE
    }

    /**
     * Get the size in bytes of a bitmap.
     * 
     * @param bitmap
     * @return size in bytes
     */
    @SuppressLint("NewApi")
    public static int getBitmapSize(Bitmap bitmap) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
            return bitmap.getByteCount();
        }
        // Pre HC-MR1
        return bitmap.getRowBytes() * bitmap.getHeight();
    }

    /**
     * Check if external storage is built-in or removable.
     * 
     * @return True if external storage is removable (like an SD card), false
     *         otherwise.
     */
    @SuppressLint("NewApi")
    public static boolean isExternalStorageRemovable() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
            return Environment.isExternalStorageRemovable();
        }
        return true;
    }

    /**
     * Get the external app cache directory.
     * 
     * @param context The context to use
     * @return The external cache dir
     */
    @SuppressLint("NewApi")
    public static File getExternalCacheDir(Context context) {
        if (hasExternalCacheDir()) {
            return context.getExternalCacheDir();
        }

        // Before Froyo we need to construct the external cache dir ourselves
        final String cacheDir = "/Android/data/" + context.getPackageName()
                + "/cache/";
        return new File(Environment.getExternalStorageDirectory().getPath()
                + cacheDir);
    }

    /**
     * Check how much usable space is available at a given path.
     * 
     * @param path The path to check
     * @return The space available in bytes
     */
    @SuppressLint("NewApi")
    public static long getUsableSpace(File path) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
            return path.getUsableSpace();
        }
        final StatFs stats = new StatFs(path.getPath());
        return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
    }

    /**
     * Get the memory class of this device (approx. per-app memory limit)
     * 
     * @param context
     * @return
     */
    public static int getMemoryClass(Context context) {
        return ((ActivityManager) context
                .getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
    }

    /**
     * Convert a given string to a lower case based on the default locate set on
     * the device.
     * 
     * @param text
     * @return
     */
    public static String toLowerCase(String text, Context context) {

        return text
                .toLowerCase(context.getResources().getConfiguration().locale);
    }

    /**
     * Sets the reports verification status.
     * 
     * @param verify verify status code.
     * @return The verification name
     */
    public static String setVerificationStatus(int verify, Context context) {

        final String s = verify == 0 ? context.getString(R.string.unverified)
                : context.getString(R.string.verified);

        return Util.capitalizeString(s);

    }

    /**
     * Calculates the center coordinate of a {@link LatLngBounds}.
     * 
     * @param bounds A {@link LatLngBounds} instance.
     * @return the center coordinate of the given bounds.
     */
    public static LatLng getCenter(LatLngBounds bounds) {
        double n = bounds.northeast.latitude;
        double e = bounds.northeast.longitude;
        double s = bounds.southwest.latitude;
        double w = bounds.southwest.longitude;

        double lat = ((n + s) / 2.0);
        double lon = ((e + w) / 2.0);

        return new LatLng(lat, lon);
    }

    /**
     * Sets which map tile to use
     * 
     * @param context The calling Activity
     * @param map The GoogleMap
     */
    public static void setMapTile(Context context, GoogleMap map) {
        // load preferences
        Preferences.loadSettings(context);
        TileOverlayOptions osm = new TileOverlayOptions()
                .tileProvider(new OpenStreetMapTileProvider());

        TileOverlayOptions mapbox = new TileOverlayOptions()
                .tileProvider(new MapBoxTileProvider());

        final String mapTile = Preferences.mapTiles;
        if (map != null) {
            map.setMapType(GoogleMap.MAP_TYPE_NONE);
            map.addTileOverlay(osm).remove();
            map.addTileOverlay(mapbox).remove();
            map.clear();
            if (mapTile.equals("google")) {
                map.setMapType(GoogleMap.MAP_TYPE_NORMAL);
            } else if (mapTile.equals("osm")) {
                map.addTileOverlay(osm);
            } else {
                map.addTileOverlay(mapbox);
            }
        }
    }

    public static void showAbout(final SherlockFragmentActivity mActivity) {
        if (Util.isTablet(mActivity.getApplicationContext())) {
            // DialogFragment.show() will take care of adding the fragment
            // in a transaction. We also want to remove any currently showing
            // dialog, so make our own transaction and take care of that here.
            FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
            Fragment prev = mActivity.getSupportFragmentManager().findFragmentByTag("dialog");
            if (prev != null) {
                ft.remove(prev);
            }
            ft.setCustomAnimations(R.anim.slide_left_in, R.anim.slide_left_out,
                    R.anim.slide_right_in, R.anim.slide_right_out);
            ft.addToBackStack(null);

            // Create and show the dialog.
            AboutFragment newFragment = AboutFragment.newInstance();
            newFragment.show(ft, "dialog");
        } else {

            Intent i = new Intent(mActivity.getApplicationContext(), AboutActivity.class);
            mActivity.startActivity(i);
            mActivity.overridePendingTransition(R.anim.home_enter,
                    R.anim.home_exit);

        }

    }

    /**
     * Check if OS version has built-in external cache dir method.
     * 
     * @return
     */
    public static boolean hasExternalCacheDir() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
    }

    public void log(String message) {
        if (MainApplication.LOGGING_MODE)
            Log.i(getClass().getName(), message);
    }

    public void log(String format, Object... args) {
        if (MainApplication.LOGGING_MODE)
            Log.i(getClass().getName(), String.format(format, args));
    }

    public void log(String message, Exception ex) {
        if (MainApplication.LOGGING_MODE)
            Log.e(getClass().getName(), message, ex);
    }
}
